<?php

/*
 * Объект-хранилище
 * объектный интерфейс к массиву данных
 */

namespace T4;

class Standard implements \Countable, \IteratorAggregate, \ArrayAccess {

	/**
	 * Данные объекта
	 * @var array 
	 */
	protected $data = array();

	/**
	 * Конструктор
	 * может принимать массив "сырых" данных для заполнения
	 * @param array $data 
	 */
	public function __construct($data = array()) {
		if (!empty($data)) {
			$class = get_called_class();
			$this->data = $class::processFromArray($data)->getData();
		}
	}

	/**
	 * Возвращает внутреннее представление данных
	 * @return array 
	 */
	public function getData() {
		return $this->data;
	}

	/**
	 * Преобразование массива в объект
	 * @param array $data 
	 * @return T4\Standard
	 */
	public function fromArray($data = array()) {
		$class = get_called_class();
		$this->data = $class::processFromArray($data)->getData();
		return $this;
	}

	/**
	 * Слияние массива данных с объектом
	 * @param array $data 
	 * @return T4\Standard
	 */
	public function mergeArray($data = array()) {
		$class = get_called_class();
		$this->data = array_merge($this->data, $class::processFromArray($data)->getData());
		return $this;
	}

	/**
	 * Преобразование объекта в массив
	 * @return array 
	 */
	public function toArray() {
		$class = get_called_class();
		return $class::processToArray($this);
	}

	/**
	 * Преобразование произвольного массива в объект Standard
	 * Можно указывать многомерный массив
	 * Если массив (или подмассив) содержит числовые ключи,
	 * то он останется массивом!
	 * 
	 * Метод статический во избежание бесконечной рекурсии
	 * 
	 * @param array $data 
	 * @return T4\Standard
	 */
	protected static function processFromArray(array $data) {

		$class = get_called_class();

		if (empty($data)) {
			return new $class;
		}

		$keys = array_keys($data);
		$numberKeys = array_filter($keys, function ($a) {
					return is_int($a);
		});

		/*
		  if ( !empty($numberKeys) ) {
		  $ret = array();
		  } else {
		  $ret = new $class;
		  }
		 * 
		 */
		$ret = new $class;

		foreach ($data as $key => $value) {

			if (is_array($value)) {

				if (!empty($numberKeys)) {
					$ret->$key = $class::processFromArray($value);
					//$ret[$key] = $class::processFromArray($value);
				} else {
					$ret->$key = $class::processFromArray($value);
				}
				
			} else {

				if (!empty($numberKeys)) {
					$ret->$key = $value;
					//$ret[$key] = $value;
				} else {
					$ret->$key = $value;
				}
				
			}
			
		}

		return $ret;
		
	}

	/**
	 * Преобразует объект в массив
	 * @param \T4\Standard $object
	 * @return array 
	 */
	protected static function processToArray($data) {

		if (is_object($data)) {
			$class = get_class($data);
			$data = $data->getData();
		} else {
			$class = get_called_class();
		}

		$ret = array();

		foreach ($data as $key => $value) {
			if ($value instanceof $class || is_array($value)) {
				$ret[$key] = $class::processToArray($value);
			} else {
				$ret[$key] = $value;
			}
		}

		return $ret;
		
	}

	/**
	 * ----------------------------------------
	 * Реализация объектного интерфейса доступа к данным
	 * ----------------------------------------
	 */
	public function __set($name, $value) {
		$this->data[$name] = $value;
	}

	public function __get($name) {
		return isset($this->data[$name]) ? $this->data[$name] : null;
	}

	public function __isset($name) {
		return isset($this->data[$name]);
	}

	public function __unset($name) {
		unset($this->data[$name]);
	}

	/**
	 * ----------------------------------------
	 * Реализация стандартных интерфейсов
	 * ----------------------------------------
	 */
	public function count() {
		return count($this->data);
	}

	public function getIterator() {
		return new \ArrayIterator($this->data);
	}
	
	public function offsetExists($offset) {
		return isset($this->data[$offset]);
	}
	
	public function offsetGet($offset) {
		return $this->data[$offset];
	}
	
	public function offsetSet($offset, $value) {
		$this->data[$offset] = $value;
	}
	
	public function offsetUnset($offset) {
		unset($this->data[$offset]);
	}
	
	/**
	 * ----------------------------------------
	 * Реализация клонирования
	 * ----------------------------------------
	 */

	public function __clone() {
		$clone = array();
		$class = get_class();
		foreach ($this->data as $key => $value)
			$clone[$key] = $value instanceof $class ? clone $value : $value;
		$this->data = $clone;
	}

}